package com.hippoDocker.uaa.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.Arrays;

/**
 * @ClassName AuthorizationServer
 * @Description TODO 用@EnableAuthorizationServer注解并继承AuthorizationServerConfigurerAdapter来配置OAuth2.0授权服务器。
 * @Author tangxl
 * @Date 2022/8/9 15:46
 **/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    /**
     * 令牌存储策略
     */
    @Autowired
    private TokenStore tokenStore;
    /**
     * 注入客户端详情服务
     */
    @Autowired
    private ClientDetailsService clientDetailsService;
    /**
     * 注入授权码服务--下方有配置
     */
    @Autowired
    private AuthorizationCodeServices authorizationCodeServices;
    /**
     * 注入认证管理器
     */
    @Autowired
    private AuthenticationManager authenticationManager;
    /**
     *注入令牌服务转换器
     */
    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;
    /**
     * 注入密码编码格式
     */
    @Autowired
    private PasswordEncoder passwordEncoder;

    /**
     * AuthorizationServerConfigurerAdapter要求配置以下几个类，这几个类是由Spring
     * 创建的独立的配置对象，它们会被Spring传入AuthorizationServerConfigurer中进行配置。
     * （1）ClientDetailsServiceConfigurer：用来配置客户端详情服务（ClientDetailsService），
     * 客户端详情信息在这里进行初始化，你能够把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
     * （2）AuthorizationServerEndpointsConfigurer：用来配置令牌（token）的访问端点和令牌服务(token services)。
     * （3）AuthorizationServerSecurityConfigurer：用来配置令牌端点的安全约束.
     */

    /**
     * 客户端详情服务配置（暂时放入内存方式）
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception
    {
        //内存方式
        /**
         * clients.inMemory()// 使用in-memory存储（零时存储在内存中）
                .withClient("c1")// client_id
                    .secret(new BCryptPasswordEncoder().encode("secret"))//客户端密钥
                .resourceIds("resource_order")//资源列表，与资源配置里面的ID对应
                //该client允许的授权类型authorization_code,password,refresh_token,implicit,client_credentials
                .authorizedGrantTypes("authorization_code", "password","client_credentials","implicit","refresh_token")
                .scopes("all")// 允许的授权范围--权限标识
                .autoApprove(false)//false 跳转到授权页面
                //加上验证回调地址
                .redirectUris("http://www.baidu.com");
         **/
        //数据库方式
         clients.withClientDetails(clientDetailsService);
    }

    /**
     * 客户端详情服务
     * @param dataSource
     * @return
     */
    @Bean
    public ClientDetailsService clientDetailsService(DataSource dataSource){
        JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
        clientDetailsService.setPasswordEncoder(passwordEncoder);
        return clientDetailsService;
    }

    /**
     * 令牌管理服务配置
     * 将客户端信息存储到数据库
     * @return
     */
    @Bean
    public AuthorizationServerTokenServices tokenService()
    {
        DefaultTokenServices service=new DefaultTokenServices();
        service.setClientDetailsService(clientDetailsService);//客户端详情服务
        service.setSupportRefreshToken(true);//支持刷新令牌
        service.setTokenStore(tokenStore);//令牌存储策略
        //令牌增强
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);
        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    }

    /**
     * 授权码存储方式
     * 设置授权码模式的授权码如何存取，暂时采用内存方式(InMemoryAuthorizationCodeServices)
     * @return
     */
    @Bean
    public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource) {
        //内存方式
//        return new InMemoryAuthorizationCodeServices();
        //数据库存储方式
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * 令牌访问端点配置
     * 授权模式：
     * (1)authenticationManager 认证管理器，与资源所有者密码（password）授权类型对应--密码模式
     * (2)userDetailService 配置此接口实现类，使用“refresh_token”刷新令牌授权类型模式的流程会包含一个检查。用来确保账户是否有效--密码模式
     * (3)authorizationCodeServices 设置授权码服务，主要用于“authorization_code”授权码类型模式--授权码模式
     * (4)implicitGrantService 设置隐形授权模式，用来管理隐形授权状态--授权码模式
     * (5)tokenGranter 实现该接口，授权将全由你来掌控实现，且会忽略上面的几个属性，当标准的授权不满足你的要求时可以使用--自定义模式
     * 配置授权端点的URL(Endpoint URLS)：
     * 配置对象pathMapping():参数一--端点URL默认连接；参数二--进行替换的URL连接，所有连接都以“/”开始的参数
     * (1)"/oauth/authorize":授权端点
     * (2)"/oauth/token":令牌端点
     * (3)"/oauth/confirm_access":用户确认授权提交端点
     * (4)"/oauth/error":授权服务错误信息端点
     * (5)"/oauth/check_token":用于资源服务访问的令牌解析端点
     * (6)"/oauth/token_key":提供公有密钥的端点，前提是使用Jwt令牌
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .authenticationManager(authenticationManager)//认证管理器--密码模式需要
                .authorizationCodeServices(authorizationCodeServices)//授权码服务--授权码模式需要
                .tokenServices(tokenService())//令牌管理服务
                .allowedTokenEndpointRequestMethods(HttpMethod.POST); //允许post提交进行访问
    }

    /**
     * 用来配置令牌端点(Token Endpoint)的安全约束(策略)
     * @param security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security)
    {
        security
                .tokenKeyAccess("permitAll()") //对应“/oauth/token_key”密钥访问是公开
                .checkTokenAccess("permitAll()") //对应“/oauth/check_token”公开--资源服务远程校验合法性
                .allowFormAuthenticationForClients(); //表单认证（申请令牌）
    }


}
